package cn.org.xwb.common.file.utils;

import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;
import io.minio.messages.Item;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Component
@Log4j2
public class MinioUtil {

    private MinioUtil() {
    }

    @Autowired
    private MinioClient minioClient;

    /**
     * 创建bucket
     *
     * @param bucket bucket名称
     */
    public Boolean createBucket(String bucket) {
        try {
            if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucket).build())) {
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucket).build());
            }
            return true;
        } catch (Exception e) {
            log.error("创建 bucket 失败，bucket:{}，\n错误信息:{}，\nerror:{}\n", bucket, e.getMessage(), e);
            return false;
        }
    }

    /**
     * 获取要上传文件的下一个上传分片索引，分片的文件名为分片的索引，
     * 分片文件对象的对象名为 chunkDirName + 分片的索引
     *
     * @return 要上传文件的下一个上传分片索引
     */
    public Integer getNextFileSliceIdx(String bucket, String objName, String chunkDirName) {
        // 文件存在
        if (fileIsExists(bucket, objName)) {
            return -1;
        }
        // 存储分片的文件夹不存在
        if (!folderIsExist(bucket, chunkDirName)) {
            return 0;
        }
        // 获取要上传文件的下一个上传分片索引
        List<Item> objs = getObjs(bucket, chunkDirName, false);
        objs.sort((o1, o2) -> {
            return Integer.valueOf(o1.objectName().substring(o1.objectName().lastIndexOf("/") + 1)) -
                    Integer.valueOf(o2.objectName().substring(o2.objectName().lastIndexOf("/") + 1));
        });
        String lastObjName = objs.get(objs.size() - 1).objectName();
        return Integer.valueOf(lastObjName.substring(lastObjName.lastIndexOf("/") + 1)) + 1;
    }

    /**
     * 将文件对象上传到 minio
     *
     * @param bucket   桶
     * @param objName  文件对象名
     * @param chunk    文件对象
     * @param mimeType 文件类型
     * @return 是否上传成功
     */
    public boolean addFileToMinio(String bucket, String objName, MultipartFile chunk, String mimeType) {
        try {
            PutObjectArgs putObjectArgs = PutObjectArgs
                    .builder()
                    .bucket(bucket)
                    .object(objName)
                    // 文件对象输入流，上传到 minio 的大小，分片大小(minio 默认要求分片最小 5MB， -1 取消限制)
                    .stream(chunk.getInputStream(), chunk.getSize(), -1)
                    .contentType(mimeType)
                    .build();
            minioClient.putObject(putObjectArgs);
            log.info("文件上传成功，bucket:{}，objName:{}", bucket, objName);
            return true;
        } catch (Exception e) {
            log.error("文件上传失败，bucket:{}，objName:{}，\n错误信息:{}，\nerror:{}\n", bucket, objName, e.getMessage(), e);
        }
        return false;
    }

    /**
     * 将文件对象上传到 minio
     *
     * @param bucket 桶
     * @param file 文件 File 对象
     * @param objName 存储在 minio 中的文件对象名
     * @return 是否成功，true-成功；false-失败
     */
    public Boolean addFileToMinio(String bucket, File file, String objName) {
        try {
            UploadObjectArgs uploadObjectArgs = UploadObjectArgs
                    .builder()
                    .bucket(bucket)
                    .object(objName)
                    .filename(file.getAbsolutePath())
                    .build();
            minioClient.uploadObject(uploadObjectArgs);
            log.info("文件上传成功，bucket:{}，objName:{}，\nfileAbsolutePath：{}", bucket, objName, file.getAbsolutePath());
            return true;
        } catch (Exception e) {
            log.error("文件上传失败，bucket:{}，objName:{}，\nfileAbsolutePath：{}\n错误信息:{}，\nerror:{}\n", bucket, objName, file.getAbsolutePath(), e.getMessage(), e);
            return false;
        }
    }

    /**
     * 获取文件的访问地址
     *
     * @return 文件访问地址
     */
    public String getFileUrl(String bucket, String objName, Integer duration, TimeUnit timeUnit) {
        String fileUrl = null;
        try {
            fileUrl = minioClient.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs
                            .builder()
                            .bucket(bucket)
                            .object(objName)
                            .method(Method.GET)
                            .expiry(duration, timeUnit)
                            .build()
            );
        } catch (Exception e) {
            log.error("获取文件的访问地址失败，bucket:{}，objName:{}，\n错误信息:{}，\nerror:{}\n", bucket, objName, e.getMessage(), e);
        }
        return fileUrl;
    }

    /**
     * 判断文件是否存在
     *
     * @param bucket  桶
     * @param objName 文件对象名
     * @return true：文件存在；false：文件不存在
     */
    public boolean fileIsExists(String bucket, String objName) {
        try {
            StatObjectArgs statObjectArgs = StatObjectArgs
                    .builder()
                    .bucket(bucket)
                    .object(objName)
                    .build();
            minioClient.statObject(statObjectArgs);
            return true;
        } catch (Exception e) {
            log.info("文件不存在，bucket:{}，objName:{}\n", bucket, objName);
        }
        return false;
    }

    /**
     * 判断文件夹是否存在
     *
     * @param bucket  桶
     * @param objName 文件夹对象名称(文件夹名称必须每层目录完整，文件夹对象名称最后不以 / 结尾)
     * @return true：存在；false：不存在
     */
    public boolean folderIsExist(String bucket, String objName) {
        try {
            ListObjectsArgs listObjectsArgs = ListObjectsArgs
                    .builder()
                    .bucket(bucket)
                    .prefix(objName)
                    // 是否递归
                    .recursive(false)
                    .build();
            // 获取对象名称前缀为 objName 的文件或文件夹对象
            // 文件夹对象名称最后没以 / 结尾，获取文件夹本身
            // 文件夹对象名称最后以 / 结尾，获取文件夹中的内容
            Iterable<Result<Item>> results = minioClient.listObjects(listObjectsArgs);
            int cnt = 0;
            for (Result<Item> result : results) {
                cnt++;
                Item item = result.get();
                // 文件夹对象名称最后没以 / 结尾，获取文件夹本身，相等即存在
                // 文件夹的名称，最后以 / 结尾，所以 objName + "/"
                if (item.isDir() && (objName + "/").equals(item.objectName())) {
                    return true;
                }
            }
            if (cnt > 0) return true;
        } catch (Exception e) {
            log.info("文件夹不存在，bucket:{}，objName:{}\n", bucket, objName);
        }
        return false;
    }

    /**
     * 从 minio 获取文件夹下的所有对象，
     * 文件夹对象名称最后需要以 / 结尾
     *
     * @param bucket    桶
     * @param objName   文件夹对象名称
     * @param recursive 是否递归获取
     * @return 文件对象
     */
    public List<Item> getObjs(String bucket, String objName, boolean recursive) {
        try {
            List<Item> objs = new ArrayList<>();
            ListObjectsArgs listObjectsArgs = ListObjectsArgs
                    .builder()
                    .bucket(bucket)
                    .prefix(objName)
                    // 是否递归
                    .recursive(recursive)
                    .build();
            // 获取对象名称前缀为 objName 的文件或文件夹对象
            Iterable<Result<Item>> results = minioClient.listObjects(listObjectsArgs);
            for (Result<Item> result : results) {
                Item item = result.get();
                objs.add(item);
            }
            return objs;
        } catch (Exception e) {
            log.error("获取文件夹中的所有对象失败，bucket:{}，objName:{}\n错误信息：{}\nerror：{}\n", bucket, objName, e.getMessage(), e);
            return null;
        }
    }

    /**
     * 从 minio 下载文件
     *
     * @param file    下载的文件保存到的文件对象
     * @param bucket  桶
     * @param objName 文件对象名称
     * @return 文件对象
     */
    public File downloadFileFromMinio(File file, String bucket, String objName) {
        GetObjectArgs getObjectArgs = GetObjectArgs
                .builder()
                .bucket(bucket)
                .object(objName)
                .build();
        try (
                InputStream inputStream = minioClient.getObject(getObjectArgs);
                FileOutputStream outputStream = new FileOutputStream(file);
        ) {
            IOUtils.copy(inputStream, outputStream);
            return file;
        } catch (Exception e) {
            log.error("从 minio 下载文件失败，bucket:{}，objName:{}\n错误信息：{}\nerror：{}\n", bucket, objName, e.getMessage(), e);
            return null;
        }
    }

    /**
     * 删除文件
     *
     * @return true-成功；false-失败
     */
    public Boolean removeObj(String bucket, String objName) {
        try {
            RemoveObjectArgs objectArgs = RemoveObjectArgs
                    .builder()
                    .bucket(bucket)
                    .object(objName)
                    .build();
            minioClient.removeObject(objectArgs);
            return true;
        } catch (Exception e) {
            log.error("删除文件失败，bucket:{}，objName:{}\n错误信息：{}\nerror：{}\n", bucket, objName, e.getMessage(), e);
            return false;
        }
    }

    /**
     * 删除文件夹，同时删除其中文件，文件夹名不要以 / 结尾
     */
    public Boolean removeFolder(String bucket, String objName) {
        try {

            // 删除文件夹中文件
            ListObjectsArgs listObjectsArgs = ListObjectsArgs
                    .builder()
                    .bucket(bucket)
                    .prefix(objName + "/")
                    .recursive(false)
                    .build();
            Iterable<Result<Item>> listObjects = minioClient.listObjects(listObjectsArgs);
            for (Result<Item> obj : listObjects) {
                RemoveObjectArgs removeObjectArgs = RemoveObjectArgs
                        .builder()
                        .bucket(bucket)
                        .object(obj.get().objectName())
                        .build();
                minioClient.removeObject(removeObjectArgs);
            }
            RemoveObjectArgs removeObjectArgs = RemoveObjectArgs
                    .builder()
                    .bucket(bucket)
                    .object(objName)
                    .build();
            minioClient.removeObject(removeObjectArgs);
            return true;
        } catch (Exception e) {
            log.error("删除文件夹失败，bucket:{}，objName:{}\n错误信息：{}\nerror：{}\n", bucket, objName, e.getMessage(), e);
            return false;
        }
    }

}
